package com.example.demo07cache.component;

import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.serializer.StringRedisSerializer;
import org.springframework.stereotype.Component;

import java.util.Collections;

@Slf4j
@Component
public class RedisLock {
    @Autowired
    private RedisTemplate<String, String> redisTemplate;

    private final StringRedisSerializer argsStringSerializer = new StringRedisSerializer();
    private final StringRedisSerializer resultStringSerializer = new StringRedisSerializer();
    private final String EXEC_RESULT = "1";

    /**
     * 分布式锁 --> 加锁
     * @param key 加锁的 key
     * @param requestId 加锁的 value，防止线程 1 把线程 2 加的锁释放掉
     * @param expireTime 锁的过期时间
     * @param retryTimes 重试次数
     * @return true 加锁成功，false 加锁失败
     */
    public boolean lock(String key, String requestId, String expireTime, int retryTimes) {
        if (retryTimes <= 0) {
            retryTimes = 1;
        }

        try {
            int count = 0;

            while(true) {
                String result = this.redisTemplate.execute(RedisScripts.LOCK_LUA_SCRIPT, this.argsStringSerializer, this.resultStringSerializer, Collections.singletonList(key), new Object[]{requestId, expireTime});
                log.debug("result:{},type:{}", result, result.getClass().getName());
                if (EXEC_RESULT.equals(result)) {
                    log.info("lock---->result:{},requestId:{}", "加锁成功", requestId);
                    return true;
                }

                if (retryTimes == ++count) {
                    log.warn("has tried {} times, failed to acquire lock for key:{}, requestId:{}", count, key, requestId);
                    return false;
                }

                log.warn("try to acquire lock {} times for key:{}, requestId:{}", count, key, requestId);
                Thread.sleep(100L);
            }
        } catch (InterruptedException e) {
            log.error("lock---->result:{},requestId:{}", "加锁异常", requestId);
            return false;
        }
    }

    /**
     * 分布式锁 --> 释放锁
     * @param key 释放锁的 key
     * @param requestId 释放锁的 value，防止线程 1 把线程 2 加的锁释放掉
     * @return true 释放锁成功，false 释放锁失败
     */
    public boolean unLock(String key, String requestId) {
        String result = this.redisTemplate.execute(RedisScripts.UNLOCK_LUA_SCRIPT, this.argsStringSerializer, this.resultStringSerializer, Collections.singletonList(key), new Object[]{requestId});
        if (EXEC_RESULT.equals(result)) {
            log.info("unLock---->result:{},requestId:{}", "解锁成功", requestId);
            return true;
        } else {
            log.info("unLock---->result:{},requestId:{}", "锁过期，解锁失败", requestId);
            return false;
        }
    }

    /**
     * Redis 有 decr 的原子递减命令，但是 decr 可以将数字减为负数，为了解决这种减为负数的问题，使用 Lua 脚本解决
     * @param key RedisKey
     * @return true: 递减成功   false: 递减失败，减到 0 不能再减了
     */
    public boolean decrToZero(String key) {
        String result = this.redisTemplate.execute(RedisScripts.DECR_TO_ZERO_LUA_SCRIPT, this.argsStringSerializer, this.resultStringSerializer, Collections.singletonList(key));
        if (result != null && Integer.parseInt(result) >= 0) {
            log.info("decr---->success--->{}", result);
            return true;
        } else {
            log.info("decr---->fail--->{}", result);
            return false;
        }
    }

    /**
     * TODO LUA 脚本 | 返回值类型
     * 扣减库存的 LUA 脚本
     */
    public boolean decrStock(String key, Integer num) {
        String result = this.redisTemplate.execute(RedisScripts.DECR_STOCK_LUA_SCRIPT, this.argsStringSerializer, this.resultStringSerializer, Collections.singletonList(key), String.valueOf(num));
        if (result != null && Integer.parseInt(result) >= 0) {
            log.info("decr---->success--->key={},num={}", key, result);
            return true;
        } else {
            log.info("decr---->fail--->key={},num={}", key, result);
            return false;
        }
    }

    /**
     * 生成一定数量的 Key，例如: ${key}-${i} = ${i} * ${i}
     * @param key key前缀
     * @param count key数量
     * @param expireTime 过期时间，单位 second
     * @return 生成的 Key 的数量
     */
    public Long addKeys(String key, Long count, Integer expireTime) {
        String result = this.redisTemplate.execute(RedisScripts.ADD_KEYS_LUA_SCRIPT, this.argsStringSerializer, this.resultStringSerializer, Collections.singletonList(key), String.valueOf(count), String.valueOf(expireTime));
        assert result != null;
        return Long.valueOf(result);
    }

    public Long bitCount(String key, Long start, Long end) {
        String result = this.redisTemplate.execute(RedisScripts.BIT_COUNT_LUA_SCRIPT, this.argsStringSerializer, this.resultStringSerializer, Collections.singletonList(key), String.valueOf(start), String.valueOf(end));
        assert result != null;
        return Long.valueOf(result);
    }
}
